package net.geant.gembus.cxf;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;

import javax.xml.namespace.QName;

import net.geant.gembus.registry_publisher.RegistryPublishService;

import org.apache.cxf.Bus;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.endpoint.ServerRegistry;
import org.apache.cxf.service.model.EndpointInfo;
import org.apache.log4j.Logger;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;

public class CXFBusListener  {
	
	private final static Logger LOG = Logger.getLogger(CXFBusListener.class);
	private final static String HTTP_PORT_PROPERTY = "org.osgi.service.http.port";
	private final static String SERVICE_IDS_CONFIG_PERSISTENT_ID = "gembus.registry.serviceids";
	
	private BundleContext bundleContext;
	private RegistryPublishService registryService;
	private Configuration serviceIdConfig;
	
	public void initialize() throws IOException {
		ServiceReference sr = bundleContext.getServiceReference(ConfigurationAdmin.class.getCanonicalName());
		ConfigurationAdmin cfg = (ConfigurationAdmin) sr.getBundle().getBundleContext().getService(sr);
		serviceIdConfig = cfg.getConfiguration(SERVICE_IDS_CONFIG_PERSISTENT_ID);		
	}
	
	private URL calculateURLToWSDL(Endpoint endpoint) throws Exception {
		URL answer = null;
		try {
			answer = new URL(endpoint.getEndpointInfo().getAddress() + "?wsdl");
		} catch (MalformedURLException e) {
			// this means that the URL was not valid, this usually happens if the endpoint is exposed via the Servlet container (default in OSGi)
			// and no requests have been received yet... When the first request enters, the servlet sets the address to the actual one
			ServiceReference sr = bundleContext.getServiceReference(ConfigurationAdmin.class.getCanonicalName());
			ConfigurationAdmin cfg = (ConfigurationAdmin) sr.getBundle().getBundleContext().getService(sr);
			String httpPort = null;
			for (Configuration config : cfg.listConfigurations(null)) {
				if (config.getProperties().get(HTTP_PORT_PROPERTY) != null) {
					httpPort = config.getProperties().get(HTTP_PORT_PROPERTY).toString();
					break;
				}
			}
			ServiceReference[] services = bundleContext.getServiceReferences("javax.servlet.Servlet", "(servlet-name=cxf-osgi-transport-servlet)");
			if (services.length != 1) {
				throw new RuntimeException("Too many or no CXF Servlets were found. Have you installed CXF for OSGi?");
			}
			
			String cxfServletContextPath = services[0].getProperty("alias").toString();
			String calculatedURLForWSDL = "http://" + InetAddress.getLocalHost().getHostName() + ":" + httpPort + cxfServletContextPath + endpoint.getEndpointInfo().getAddress() + "?wsdl";
			answer = new URL(calculatedURLForWSDL);
			
		}
		return answer;
	}
	
	public void busRegistered(Bus bus) throws Exception {
		List<Server> servers = bus.getExtension(ServerRegistry.class).getServers();
		for (Server server : servers) {
			URL url = calculateURLToWSDL(server.getEndpoint());
			URLConnection conn = url.openConnection();
			BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
			String inputLine;
			String wsdl = "";
			while ((inputLine = in.readLine()) != null) {
				wsdl += inputLine;
			}
			in.close();
			LOG.info("WSDL obtained for service " + server.getEndpoint().getService().getName() + ": " + wsdl);
			registerService(server.getEndpoint().getEndpointInfo(), wsdl);
		}
		
	}
	
	public void busRemoved(Bus bus) throws IOException, InvalidSyntaxException {
		if (bus == null) {
			return;
		}
			
		List<Server> servers = bus.getExtension(ServerRegistry.class).getServers();
		for (Server server : servers) {
			removeService(server.getEndpoint().getEndpointInfo());
		}
	}

	private void registerService(EndpointInfo endpointInfo, String wsdl) {
		String serviceId = resolveServiceId(endpointInfo);
		// finally, go ahead and register the service
		try {
			registryService.publishService(serviceId, wsdl);
		} catch (Exception e) {
			LOG.error("An exception occurred while registering the service: " + e);
		}
	}
	
	private void removeService(EndpointInfo endpointInfo) {
		String serviceId = resolveServiceId(endpointInfo);
		// finally, go ahead and remove the service
		try {
			registryService.removeService(serviceId);
		} catch (Exception e) {
			LOG.error("An exception occurred while removing the service: " + e);
		}
	}

	private String resolveServiceId(EndpointInfo endpointInfo) {
		// QName is the qualified name of the service, toString representation is {targetNamespace}serviceName
		QName serviceQName = endpointInfo.getService().getName();
		// Start using QName.toString() as default service ID ...
		String serviceId = endpointInfo.getService().getName().toString(); 
		// ... Check if there is this service has a custom ID defined in a configuration entry; if so, use it
		if (serviceIdConfig != null && serviceIdConfig.getProperties() != null && serviceIdConfig.getProperties().get(serviceQName.toString()) != null) {
			serviceId = serviceIdConfig.getProperties().get(serviceQName.toString()).toString();
		}
		return serviceId;
	}

	public BundleContext getBundleContext() {
		return bundleContext;
	}

	public void setBundleContext(BundleContext bundleContext) {
		this.bundleContext = bundleContext;
	}

	public RegistryPublishService getRegistryService() {
		return registryService;
	}

	public void setRegistryService(RegistryPublishService publishService) {
		this.registryService = publishService;
	}

}